#include "src/interface/input.h"

#include <QDebug>
#include <QTextStream>
#include <QStringList>

#include "src/defines/primitive.h"

#include "src/scene/cuboidPrimitive.h"
#include "src/scene/spherePrimitive.h"
#include "src/scene/cylinderPrimitive.h"
#include "src/scene/conePrimitive.h"
#include "src/scene/combinedObject.h"
#include "src/scene/ambientLight.h"
#include "src/scene/pointLight.h"
#include "src/scene/areaLight.h"

#include "src/scene/raytracing.h"
#include "src/scene/photonMapping.h"

#include "src/defines/primitive.h"
#include "src/defines/light.h"
#include "src/defines/antialiasingType.h"
#include "src/defines/filterType.h"

Input::Input(){
    file = NULL;
}

Input::~Input(){
}

#if USE_CONSOLE
std::vector<Object *> Input::parseScene(QFile * _file, Scene *_s, bool _readOnlyObjects){
#else //USE_CONSOLE
std::vector<Object *> Input::parseScene(QFile * _file, Scene *_s, GLWidget *_glWidget, bool _readOnlyObjects){
#endif //USE_CONSOLE
    file = _file;

    if(!file->open(QFile::ReadOnly))
    {
        std::cout <<"NAO ABRIU!" << std::endl;
        exit(1);
    }

	char type;

	std::vector<Object *> objects;
	std::vector<Light *> lights;
	Object *lastObject = NULL;

	QTextStream stream(file);
//	stream.setEncoding(QTextStream::UnicodeUTF8);
    bool photonMappinInfo = false;



	while(stream >> type, !stream.atEnd())
	{
//        qDebug() << "type " << type;
//        int nhe;
//        std::cin >> nhe;
	    switch(type)
	    {
	        case 'i'://i - informação
	        {
//	            std::cout <<"i" << std::endl;

	            if(_readOnlyObjects) continue;
	            int depth;
	            double backgroundR, backgroundG, backgroundB;
	            stream  >> depth >> backgroundR >> backgroundG >> backgroundB;
	            Raytracing *raytracing = _s->getRaytracing();
	            PhotonMapping *photonMapping = _s->getPhotonMapping();
                raytracing->setDepth(depth);
                photonMapping->setDepth(depth);
                _s->setBackgroundColor(Color(backgroundR, backgroundG, backgroundB));

                break;
	        }
	        case 'm'://m - informação de photon mapping
	        {
//	            std::cout <<"m" << std::endl;

	            if(_readOnlyObjects) continue;

	            photonMappinInfo = true;

	            unsigned int photonsDepth;
                unsigned int maxPhotons;
                unsigned int causticMaxPhotons;

                unsigned int maxNearestPhotons;
                unsigned int causticMaxNearestPhotons;
                double maxRadius;
                double causticMaxRadius;

                unsigned short filter;
                double coneFilterConstant;

                int keepFirstBounce;

                unsigned short testReflexion;

	            stream  >> photonsDepth >> maxPhotons >> maxNearestPhotons >> maxRadius
                        >> causticMaxPhotons >> causticMaxNearestPhotons >> causticMaxRadius
                        >> filter >> coneFilterConstant >> testReflexion >> keepFirstBounce;

	            PhotonMapping *photonMapping = _s->getPhotonMapping();
                photonMapping->setPhotonDepth(photonsDepth);
                photonMapping->setMaxPhotons(maxPhotons);
                photonMapping->setMaxNearestPhotons(maxNearestPhotons);
                photonMapping->setMaxRadius(maxRadius);
                photonMapping->setCausticMaxPhotons(causticMaxPhotons);
                photonMapping->setCausticMaxNearestPhotons(causticMaxNearestPhotons);
                photonMapping->setCausticMaxRadius(causticMaxRadius);
                photonMapping->setFilter(filter);
                photonMapping->setConeFilterConstant(coneFilterConstant);
                photonMapping->setTestReflexion(testReflexion);
                photonMapping->setKeepFirstBounce(keepFirstBounce != 0);

                break;
	        }
	        case 'o'://o - objeto composto
	        {
//	            std::cout <<"o" << std::endl;

                int id, operationType, parentId;
                double ambientR, ambientG, ambientB, ambientA, diffuseR, diffuseG, diffuseB, diffuseA, specularR, specularG, specularB, specularA, specularPower, ka, kd, ks, kt, refractiveIndex;
                Vector3D center, relativePosition;
                Matrix m(4,4);
                Matrix mInverse(4,4);


                stream  >> id >> operationType >> parentId >> ambientR >> ambientG >> ambientB >> ambientA
                        >> diffuseR >> diffuseG >> diffuseB >> diffuseA
                        >> specularR >> specularG >> specularB >> specularA
                        >> specularPower >> ka >> kd >> ks >> kt >> refractiveIndex
                        >> center.x >> center.y >> center.z >> relativePosition.x >> relativePosition.y >> relativePosition.z
                        >> m[0][0] >> m[0][1] >> m[0][2] >> m[0][3]
                        >> m[1][0] >> m[1][1] >> m[1][2] >> m[1][3]
                        >> m[2][0] >> m[2][1] >> m[2][2] >> m[2][3]
                        >> m[3][0] >> m[3][1] >> m[3][2] >> m[3][3]
                        >> mInverse[0][0] >> mInverse[0][1] >> mInverse[0][2] >> mInverse[0][3]
                        >> mInverse[1][0] >> mInverse[1][1] >> mInverse[1][2] >> mInverse[1][3]
                        >> mInverse[2][0] >> mInverse[2][1] >> mInverse[2][2] >> mInverse[2][3]
                        >> mInverse[3][0] >> mInverse[3][1] >> mInverse[3][2] >> mInverse[3][3];

//                stream      >> objectType;
                Object *obj = new CombinedObject( operationType, center, //relativePosition,
                                          Material(Color(ambientR, ambientG, ambientB, ambientA),
                                                   Color(diffuseR, diffuseG, diffuseB, diffuseA),
                                                   Color(specularR, specularG, specularB, specularA),
                                                   specularPower, ka, kd, ks, kt, refractiveIndex),
                                          m, mInverse, id );

                lastObject = obj;
                if(parentId == -1){
                    objects.push_back(obj);
                }
                else{
                    bool finded = false;
                    for(unsigned int i = 0; i < objects.size(); i++){
                        Object *parent = objects.at(i);
                        if(parent->getId() == parentId){
                            finded = true;
                            ((CombinedObject *)parent)->addObject(obj);
                            break;
                        }
                    }
                    if(!finded) std::cout <<"Não foi achado o pai " << parentId << " do objeto " << id <<std::endl;
                }

                break;
            }
	        case 'p'://p - primitiva
	        {
//	            std::cout <<"p" << std::endl;

                int id, objectType, parentId;
                double ambientR, ambientG, ambientB, ambientA, diffuseR, diffuseG, diffuseB, diffuseA, specularR, specularG, specularB, specularA, specularPower, ka, kd, ks, kt, refractiveIndex;
                Vector3D center, relativePosition;
                Matrix m(4,4);
                Matrix mInverse(4,4);


                stream  >> id >> objectType >> parentId >> ambientR >> ambientG >> ambientB >> ambientA
                        >> diffuseR >> diffuseG >> diffuseB >> diffuseA
                        >> specularR >> specularG >> specularB >> specularA
                        >> specularPower >> ka >> kd >> ks >> kt >> refractiveIndex
                        >> center.x >> center.y >> center.z >> relativePosition.x >> relativePosition.y >> relativePosition.z
                        >> m[0][0] >> m[0][1] >> m[0][2] >> m[0][3]
                        >> m[1][0] >> m[1][1] >> m[1][2] >> m[1][3]
                        >> m[2][0] >> m[2][1] >> m[2][2] >> m[2][3]
                        >> m[3][0] >> m[3][1] >> m[3][2] >> m[3][3]
                        >> mInverse[0][0] >> mInverse[0][1] >> mInverse[0][2] >> mInverse[0][3]
                        >> mInverse[1][0] >> mInverse[1][1] >> mInverse[1][2] >> mInverse[1][3]
                        >> mInverse[2][0] >> mInverse[2][1] >> mInverse[2][2] >> mInverse[2][3]
                        >> mInverse[3][0] >> mInverse[3][1] >> mInverse[3][2] >> mInverse[3][3];

                Object *obj;
                switch(objectType){
                    case CUBOID:{
                        double minX, minY, minZ, maxX, maxY, maxZ;
                        stream >> minX >> minY >> minZ >> maxX >> maxY >> maxZ;//      >> objectType;
                        obj = new CuboidPrimitive( Vector3D(minX, minY, minZ),
                                                   Vector3D(maxX, maxY, maxZ),
                                                   center, //relativePosition,
                                                   Material(Color(ambientR, ambientG, ambientB, ambientA),
                                                            Color(diffuseR, diffuseG, diffuseB, diffuseA),
                                                            Color(specularR, specularG, specularB, specularA),
                                                            specularPower, ka, kd, ks, kt, refractiveIndex),
                                                   m, mInverse, id );
                        break;
                    }
                    case SPHERE:{
                        double radius;
                        stream >> radius;//      >> objectType;
                        obj = new SpherePrimitive( center, //relativePosition,
                                                   radius,
                                                   Material(Color(ambientR, ambientG, ambientB, ambientA),
                                                            Color(diffuseR, diffuseG, diffuseB, diffuseA),
                                                            Color(specularR, specularG, specularB, specularA),
                                                            specularPower, ka, kd, ks, kt, refractiveIndex),
                                                   m, mInverse, id );
                        break;
                    }
                    case CYLINDER:{
                        double radius, height;
                        stream >> radius >> height;//      >> objectType;
                        obj = new CylinderPrimitive( center, //relativePosition,
                                                     radius, height,
                                                     Material(Color(ambientR, ambientG, ambientB, ambientA),
                                                              Color(diffuseR, diffuseG, diffuseB, diffuseA),
                                                              Color(specularR, specularG, specularB, specularA),
                                                              specularPower, ka, kd, ks, kt, refractiveIndex),
                                                     m, mInverse, id );
                        break;
                    }
                    case CONE:{
                        double radius, height;
                        stream >> radius >> height;//      >> objectType;
                        obj = new ConePrimitive( center, //relativePosition,
                                                 radius,
                                                 height,
                                                 Material(Color(ambientR, ambientG, ambientB, ambientA),
                                                          Color(diffuseR, diffuseG, diffuseB, diffuseA),
                                                          Color(specularR, specularG, specularB, specularA),
                                                          specularPower, ka, kd, ks, kt, refractiveIndex),
                                                 m, mInverse, id );
                        break;
                    }
                }

                lastObject = obj;
                if(parentId == -1){
                    objects.push_back(obj);
                }
                else{
                    bool finded = false;
                    for(unsigned int i = 0; i < objects.size(); i++){
                        if(tryObject(parentId, objects.at(i), obj))
                        {
                            finded = true;
                            break;
                        }
                    }
                    if(!finded) std::cout <<"Não foi achado o pai " << parentId << " do objeto " << id <<std::endl;
                }

                break;
            }
            case 't'://t - textura
            {
//	            std::cout <<"t" << std::endl;

                QString texturePathName;
                int repeatU, repeatV, mappingType, coloringType;

                stream  >> texturePathName >> repeatU >> repeatV >> mappingType >> coloringType;

                lastObject->addTexture(new Texture(
                                          #if !USE_CONSOLE
                                          _glWidget,
                                          #endif //!USE_CONSOLE
                                          texturePathName,
                                          repeatU, repeatV,
                                          coloringType, mappingType));
                break;
            }
            case 'b'://b - bump mapping
            {
//	            std::cout <<"b" << std::endl;

                QString bumpMappingPathName;
                int repeatU, repeatV, mappingType;
                double intensity;
                int loop;

                stream  >> bumpMappingPathName >> repeatU >> repeatV >> mappingType >> intensity >> loop;

                lastObject->addBumpMapping(new BumpMapping(
                                          bumpMappingPathName,
                                          repeatU, repeatV,
                                          mappingType, intensity, (bool)loop));
                break;
            }
            case 'l'://l - luz
            {
//	            std::cout <<"l" << std::endl;

	            if(_readOnlyObjects) continue;
                int lightType;
                stream >> lightType;
                //AMBIENT_LIGHT - luz ambiente
                //POINT_LIGHT - luz pontual
                //AREA_LIGHT - luz em area
                int idLight = _s->nextLightsID();
                switch(lightType)
                {
                    case AMBIENT_LIGHT:
                    {
                        double colorR, colorG, colorB;
                        stream  >> colorR >> colorG >> colorB;
                        lights.push_back(new AmbientLight(idLight++, Color(colorR, colorG, colorB)));
                        break;
                    }
                    case POINT_LIGHT:
                    {
                        double colorR, colorG, colorB, positionX, positionY, positionZ;
                        stream  >> colorR >> colorG >> colorB >> positionX >> positionY >> positionZ;
                        double energy = 1;
                        if(photonMappinInfo)
                            stream  >> energy;
                        lights.push_back(new PointLight(idLight++,
                                                        Vector3D(positionX, positionY, positionZ),
                                                        Color(colorR, colorG, colorB), energy
                                                        )
                                         );
                        break;
                    }
                    case AREA_LIGHT:
                    {
                        double colorR, colorG, colorB, positionX, positionY, positionZ, aX, aY, aZ, bX, bY, bZ;
                        stream  >> colorR >> colorG >> colorB >> positionX >> positionY >> positionZ >>
                                aX >> aY >> aZ >> bX >> bY >> bZ;
                        double energy = 1;
                        if(photonMappinInfo)
                            stream  >> energy;
                        lights.push_back(
                                         new AreaLight(idLight++, Vector3D(positionX, positionY, positionZ),
                                                       Vector3D(aX, aY, aZ), Vector3D(bX, bY, bZ),
                                                       Color(colorR, colorG, colorB), energy
                                                       )
                                         );
                        break;
                    }
                }

                break;
            }
            case 'c'://c - camera
            {
//	            std::cout <<"c" << std::endl;

	            if(_readOnlyObjects) continue;
                double eyeX, eyeY, eyeZ, lookAtX, lookAtY, lookAtZ, upX, upY, upZ, imagePlane, zoom;
                int m, n;
                stream  >> eyeX >> eyeY >> eyeZ >> lookAtX >> lookAtY >> lookAtZ >> upX >> upY >> upZ >> m >> n >> imagePlane >> zoom;
                Camera *camera = _s->getCamera();
                camera->setEye(Vector3D(eyeX, eyeY, eyeZ));
                camera->setLookAt(Vector3D(lookAtX, lookAtY, lookAtZ));
                camera->setUp(Vector3D(upX, upY, upZ));
                camera->setMN(m, n);
                camera->setImagePlane(imagePlane);
                camera->setZoom(zoom);
                //setando esses dois só pra garantir
                camera->setAntialiasing(0, 1, 1);
                camera->setDepthOfField(false);

                break;
            }
            case 's'://s - antialiasing
            {
//	            std::cout <<"s" << std::endl;

	            if(_readOnlyObjects) continue;
                //REGULAR - regular
                //JITTERING - jittering
                //RANDOM - random
                int antialiasingType, mPP, nPP;
                stream >> antialiasingType >> mPP >> nPP;
                _s->getCamera()->setAntialiasing(antialiasingType, mPP, nPP);

                break;
            }
            case 'f'://f - filtro
            {
//	            std::cout <<"f" << std::endl;

	            if(_readOnlyObjects) continue;
                //MEAN - media
                //GAUSSIAN - gaussiano
                //LAPLACIAN - laplaciano
                //WEIGHTED_AVERANGE - pedia ponderada
                int filterType;
                stream >> filterType;
                Camera *camera = _s->getCamera();
                camera->setFilterType(filterType);
                switch(filterType)
                {
                    case WEIGHTED_AVERANGE:
                    case MEAN:
                        break;
                    case GAUSSIAN:
                    {
                        double sigma;
                        stream >> sigma;

                        camera->setFilterGaussianSigma(sigma);
                        break;
                    }
                    case LAPLACIAN:
                    {
                        int neigborhood, center;
                        stream >> neigborhood >> center;

                        camera->setFilterLaplacian(neigborhood, center);
                        break;
                    }
                }

                break;
            }
            case 'd'://d - depth of field
            {
//	            std::cout <<"d" << std::endl;

	            if(_readOnlyObjects) continue;
                double focalDistance, radius;
                stream >> focalDistance >> radius;
                _s->getCamera()->setDepthOfField(focalDistance, radius);

                break;
            }
            default:
                break;
            case '#':
                stream.readLine();
                break;
	    }

        //tirando o erro que tem pra ler o \n
//        stream >> type;
	}

    if(!_readOnlyObjects)
        _s->setObjectsAndLights(objects, lights);
    else
        _s->setLights(lights);

	file->close();
	return objects;
}

bool Input::tryObject(int _id, Object *_parentMaybe, Object *_obj)
{
    if(_parentMaybe->getId() == _id){
        ((CombinedObject *)_parentMaybe)->addObject(_obj);
        return true;
    }
    else
    {
        if(!_parentMaybe->isPrimitive())
        {
            for(unsigned int i = 0; i < ((CombinedObject *)_parentMaybe)->getObjectsSize(); i++){
                if(tryObject(_id, ((CombinedObject *)_parentMaybe)->getObject(i), _obj))
                    return true;
            }
        }
    }
    return false;
}
